package backend.component;

import backend.Instruction.*;
import backend.Register.RISCVreg;
import backend.Register.physicalFReg;
import backend.Register.physicalReg;
import midend.IR_Value.IR_BaseBlock;
import midend.IR_Value.IR_Value;
import backend.Register.initReg;

import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;

public class RISCVfunction extends RISCVcomponent{
    private static int WORD_SIZE = 8;
    private static int WORD_ALIGN = 4;
    private String name;
    private LinkedList<RISCVblock> blocks;
    //留出的内存
    private int allocsize;
    //每个传入的参数分配出的内存  主要是额外的参数大小
    private int argssize;
    private RISCVblock exit = null;
    //反向的 无参数为真 有参数为假
    private boolean hasPara;
    private int stacksize;
    private int statckoffest;
    private int paramStackSize;

    private boolean isBig = false;
    private LinkedHashMap<LinkedHashMap<IR_BaseBlock, IR_Value>,String> phiDestandSrc;
    private LinkedHashMap<String,String> paratoreg;


    public RISCVfunction(String name,LinkedHashMap<String,String> paratoreg,int paramStackSize) {
        this.name = name;
        blocks = new LinkedList<>();
        allocsize = 0;
        argssize = 0;
        this.paratoreg = paratoreg;
        if(!paratoreg.isEmpty()){
            hasPara = true;
        }
        else{
            hasPara = false;
        }
        this.paramStackSize = paramStackSize;
        phiDestandSrc = new LinkedHashMap<>();
    }

    public RISCVfunction(String name, boolean hasPara) {
        this.name = name;
        this.hasPara = hasPara;
        blocks = new LinkedList<>();
        allocsize = 0;
        argssize = 0;
    }

    public void addPhilist(LinkedHashMap<IR_BaseBlock,IR_Value> src,String dest){
        this.phiDestandSrc.put(src,dest);
    }

    public LinkedHashMap<LinkedHashMap<IR_BaseBlock, IR_Value>, String> phiDestandSrc() {
        return phiDestandSrc;
    }

    public void setArgsSize(int size) {
        argssize = size;
    }

    public int getArgsSize() {
        return argssize;
    }

    public int getAllocSize() {
        return allocsize;
    }

    public void addAllocSize(int size) {
        allocsize += size;
    }

    public int getStacksize(){
        int size = WORD_SIZE + argssize + allocsize;
        if (size % WORD_SIZE != 0)
            size += WORD_ALIGN;
        return size;
    }
    public LinkedHashMap<String, String> getparatoreg() {
        return paratoreg;
    }

    public String getaregfromname(String name) {
        return paratoreg.get(name);
    }

    public void setStackTopOffset(int stackTop) {
        if(stackTop + WORD_SIZE > statckoffest){
            this.statckoffest = stackTop + WORD_SIZE;
        }
    }

    public boolean isThis(String tmp){
        return this.name.equals(tmp.startsWith("@") ? tmp.substring(1) : tmp);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void addBlocks(RISCVblock b){
        blocks.add(b);
    }

    public LinkedList<RISCVblock> getBlocks() {
        return blocks;
    }


    public RISCVblock getFirstBlock() {
        return blocks.getFirst();
    }

    public RISCVblock getExitBlock() {
        return exit;
    }

    public void setExitBlock(RISCVblock exit) {
        this.exit = exit;
    }

    public void addInstBefore(RISCVinst pos_instr, RISCVinst inst) {
        for (RISCVblock block : blocks) {
            if (block.getInstructionlist().contains(pos_instr)) {
                block.insertBefore(pos_instr, inst);
                break;
            }
        }
    }

    public void addInstAfter(RISCVinst pos_instr, RISCVinst inst) {
        for (RISCVblock block : blocks) {
            if (block.getInstructionlist().contains(pos_instr)) {
                block.insertAfter(pos_instr, inst);
                break;
            }
        }
    }

    public void setBlocks(LinkedList<RISCVblock> blocks) {
        this.blocks = blocks;
    }

    public boolean hasPara() {
        return hasPara;
    }

    public void instAlignStack(int oldStackSize) {
        if (hasPara) {
            return;
        }
        for (RISCVblock Block : blocks) {
            for (RISCVinst instr : Block.getInstructionlist()) {
                //如果 instr 是 RISCVmove 类型，则将其转换为 RISCVmove 类型，并将其赋值给 move 变量
                if (instr instanceof RISCVmove move && move.getNeedChange()) {
                    int rightSize = getStacksize() + ((RISCVimm) (move.getSrc())).getImm() - oldStackSize;
                    move.setSrc(new RISCVimm(rightSize, false));
                }
            }
        }
    }
    //根据绝大多数 RISC‑V（或其他 RISC）ABI 约定，寄存器分为 caller‑saved（如 t0–t6）和 callee‑saved（如 s0–s11）两类。
    //caller‑saved：调用者使用前需要自己保存
    //callee‑saved（s 寄存器）：被调用函数如果要用（写）这些寄存器，必须在函数入口保存原值，函数退出前恢复原值。
    public void reserveSRegs(boolean isFloat) {
        HashSet<RISCVreg> needChange = new HashSet<>();
        for (RISCVblock riscVblock : blocks) {
            for (RISCVinst instr : riscVblock.getInstructionlist()) {
                if (instr instanceof RISCVbinary) {
                    if (((RISCVbinary) instr).getDst() instanceof physicalReg dst && !isFloat && dst.isSReg()) {
                        needChange.add(dst);
                    } else if (((RISCVbinary) instr).getDst() instanceof physicalFReg dst && isFloat && dst.isSReg()) {
                        needChange.add(dst);
                    }
                } else if (instr instanceof RISCVload) {
                    if (((RISCVload) instr).getDst() instanceof physicalReg dst && !isFloat && dst.isSReg()) {
                        needChange.add(dst);
                    } else if (((RISCVload) instr).getDst() instanceof physicalFReg dst && isFloat && dst.isSReg()) {
                        needChange.add(dst);
                    }
                } else if (instr instanceof RISCVmove) {
                    if (((RISCVmove) instr).getDst() instanceof physicalReg dst && !isFloat && dst.isSReg()) {
                        needChange.add(dst);
                    } else if (((RISCVmove) instr).getDst() instanceof physicalFReg dst && isFloat && dst.isSReg()) {
                        needChange.add(dst);
                    }
                } else if (instr instanceof RISCVconversion) {
                    if (((RISCVconversion) instr).getDst() instanceof physicalReg dst && !isFloat && dst.isSReg()) {
                        needChange.add(dst);
                    } else if (((RISCVconversion) instr).getDst() instanceof physicalFReg dst && isFloat && dst.isSReg()) {
                        needChange.add(dst);
                    }
                } else if (instr instanceof RISCVla) {
                    if (((RISCVla) instr).getDst() instanceof physicalReg dst && !isFloat && dst.isSReg()) {
                        needChange.add(dst);
                    }
                }
            }
        }
        helpReserve(needChange);
    }

    private void helpReserve(HashSet<RISCVreg> needChange) {
        for (RISCVreg reg : needChange) {
            if (reg instanceof physicalReg) {
                // 对每个要保存的寄存器，先在 reg 对象上记录一个 spillPlace（栈帧偏移）
                reg.setSpillspace(getStacksize());
                // 在函数入口（第一个基本块的首条指令后）插入 store，压寄存器到栈
                insertStoreInstr(reg, false);
                // 在函数退出（exit block 最后一条指令前）插入 load，恢复寄存器
                insertLoadInstr(reg, false);
                // 记录总共占用了多少栈空间（8 字节对齐）
                allocsize += 8;
            } else if (reg instanceof physicalFReg) {
                reg.setSpillspace(getStacksize());
                insertStoreInstr(reg, true);
                insertLoadInstr(reg, true);
                allocsize += 8;
            }
        }
    }

    private void insertStoreInstr(RISCVreg reg, boolean isFloat) {
        String type = isFloat ? "fsd" : "sd";
        if (getStacksize() >= -2048 && getStacksize() <= 2047) {
            RISCVstore store = new RISCVstore(Arrays.asList(reg, initReg.SP,new RISCVimm(getStacksize(), true)), type);
            addInstAfter(getFirstBlock().getInstructionlist().getFirst(), store);
        } else {
            // get 5  是 t0
            RISCVmove move = new RISCVmove(Arrays.asList(initReg.phyRegs.get(5), new RISCVimm(getStacksize(), false)));
            RISCVbinary add = new RISCVbinary("add",Arrays.asList(initReg.phyRegs.get(5), initReg.SP, initReg.phyRegs.get(5)));
            RISCVstore store = new RISCVstore(Arrays.asList(reg, initReg.phyRegs.get(5), new RISCVimm(0, true)), type);
            if(isBig){
                getFirstBlock().addInstructionlistatindex(move,2);
                addInstAfter(move, add);
                addInstAfter(add, store);
            }
            else {
                addInstAfter(getFirstBlock().getInstructionlist().getFirst(), move);
                addInstAfter(move, add);
                addInstAfter(add, store);
            }
        }
    }

    private void insertLoadInstr(RISCVreg reg, boolean isFloat) {
        String type = isFloat ? "fld" : "ld";
        if (getStacksize() >= -2048 && getStacksize() <= 2047) {
            RISCVload load = new RISCVload(Arrays.asList(reg,  initReg.SP,new RISCVimm(getStacksize(), true)), type);
            addInstBefore(getPreInstr(getExitBlock().getInstructionlist().getLast(), getExitBlock().getInstructionlist()), load);
        } else {
            RISCVmove move = new RISCVmove(Arrays.asList(initReg.phyRegs.get(5), new RISCVimm(getStacksize(), false)));
            RISCVbinary add = new RISCVbinary("add", Arrays.asList(initReg.phyRegs.get(5), initReg.SP, initReg.phyRegs.get(5)));
            RISCVload load = new RISCVload(Arrays.asList(reg, initReg.phyRegs.get(5), new RISCVimm(0, true)), type);
            /*if (isBig) {
                int z = getExitBlock().getInstructionlist().size();
                getExitBlock().addInstructionlistatindex(load, z - 2);
                //addInstBefore(getPreInstr(getExitBlock().getInstructionlist().getLast(), getExitBlock().getInstructionlist()), load);
                addInstBefore(load, add);
                addInstBefore(add, move);
            } else {*/
                addInstBefore(getPreInstr(getExitBlock().getInstructionlist().getLast(), getExitBlock().getInstructionlist()), load);
                addInstBefore(load, add);
                addInstBefore(add, move);
            //}
        }
    }

    private RISCVinst getPreInstr(RISCVinst instr, LinkedList<RISCVinst> list) {
        int index = list.indexOf(instr);
        if (index == 0) {
            return null;
        } else {
            return list.get(index - 1);
        }
    }

    public void deleteBlock(RISCVblock b){
        this.blocks.remove(b);
    }

    public boolean isBig() {
        return isBig;
    }

    public void setBig(boolean big) {
        isBig = big;
    }
}
